/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package phases

import (
	"testing"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
)

func TestEnsureAgentUpgrade_findBKEAgentDeployerVersionInSubImage(t *testing.T) {
	e := &EnsureAgentUpgrade{BasePhase: phaseframe.BasePhase{}}
	tests := []struct {
		name     string
		subImage phaseutil.SubImage
		wantVer  string
		wantOK   bool
	}{
		{
			name: "found with tag",
			subImage: phaseutil.SubImage{
				Images: []phaseutil.Image{
					{Name: "nginx", Tag: []string{"latest"}},
					{Name: "bkeagent-deployer", Tag: []string{"v2.1.0"}},
				},
			},
			wantVer: "v2.1.0",
			wantOK:  true,
		},
		{
			name: "match by PodInfo",
			subImage: phaseutil.SubImage{
				Images: []phaseutil.Image{
					{
						Name: "custom-name",
						UsedPodInfo: []phaseutil.PodInfo{{
							PodPrefix: "bkeagent-deployer",
							NameSpace: "cluster-system",
						}},
						Tag: []string{"v3.0.1"},
					},
				},
			},
			wantVer: "v3.0.1",
			wantOK:  true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ver, ok := e.findBKEAgentDeployerVersionInSubImage(tt.subImage)
			assert.Equal(t, tt.wantOK, ok)
			assert.Equal(t, tt.wantVer, ver)
		})
	}
}

func TestEnsureAgentUpgrade_getCurrentBKEAgentDeployerVersionFromStatus(t *testing.T) {
	tests := []struct {
		name     string
		addons   []confv1beta1.Product
		expected string
	}{
		{
			name: "found bkeagent-deployer",
			addons: []confv1beta1.Product{{
				Name: "bkeagent-deployer", Version: "v2.1.0",
			}},
			expected: "v2.1.0",
		},
		{
			name: "not found",
			addons: []confv1beta1.Product{{
				Name: "other", Version: "v1.0",
			}},
			expected: "",
		},
		{
			name:     "empty status",
			addons:   []confv1beta1.Product{},
			expected: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			bkeCluster := &bkev1beta1.BKECluster{}
			bkeCluster.Status.AddonStatus = tt.addons
			e := &EnsureAgentUpgrade{}
			got := e.getCurrentBKEAgentDeployerVersionFromStatus(bkeCluster)
			assert.Equal(t, tt.expected, got)
		})
	}
}

func TestEnsureAgentUpgrade_isAgentDeployerImage(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	tests := []struct {
		name  string
		image phaseutil.Image
		want  bool
	}{
		{"by name", phaseutil.Image{Name: "bkeagent-deployer"}, true},
		{"by podinfo", phaseutil.Image{
			UsedPodInfo: []phaseutil.PodInfo{{
				PodPrefix: bkeagentDeployerName,
				NameSpace: bkeagentDeployerNamespace,
			}},
		}, true},
		{"no match", phaseutil.Image{Name: "other"}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, e.isAgentDeployerImage(tt.image))
		})
	}
}

func TestEnsureAgentUpgrade_findAgentDeployerImageInSubImage(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	subImage := phaseutil.SubImage{
		SourceRepo: "registry.example.com",
		TargetRepo: "",
		Images: []phaseutil.Image{
			{
				Name: "bkeagent-deployer",
				Tag:  []string{"v2.1.0"},
			},
		},
	}

	image, found := e.findAgentDeployerImageInSubImage(subImage)
	assert.True(t, found)
	assert.Equal(t, "registry.example.com/bkeagent-deployer:v2.1.0", image)
}

func TestEnsureAgentUpgrade_findAgentDeployerImageInSubImage_NoTag(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	subImage := phaseutil.SubImage{
		SourceRepo: "registry.example.com",
		Images: []phaseutil.Image{
			{Name: "bkeagent-deployer", Tag: []string{}},
		},
	}

	image, found := e.findAgentDeployerImageInSubImage(subImage)
	assert.False(t, found)
	assert.Equal(t, "", image)
}

func TestEnsureAgentUpgrade_findAgentDeployerImageInPatchConfig(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	patchCfg := &phaseutil.PatchConfig{
		Repos: []phaseutil.Repo{
			{
				SubImages: []phaseutil.SubImage{
					{
						SourceRepo: "registry.example.com",
						Images: []phaseutil.Image{
							{Name: "bkeagent-deployer", Tag: []string{"v3.0.1"}},
						},
					},
				},
			},
		},
	}

	image, err := e.FindAgentDeployerImageInPatchConfig(patchCfg)
	assert.NoError(t, err)
	assert.Equal(t, "registry.example.com/bkeagent-deployer:v3.0.1", image)
}

func TestEnsureAgentUpgrade_findAgentDeployerImageInPatchConfig_NotFound(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	patchCfg := &phaseutil.PatchConfig{
		Repos: []phaseutil.Repo{
			{
				SubImages: []phaseutil.SubImage{
					{
						Images: []phaseutil.Image{
							{Name: "other", Tag: []string{"latest"}},
						},
					},
				},
			},
		},
	}

	_, err := e.FindAgentDeployerImageInPatchConfig(patchCfg)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "未在 patch 配置中找到 bkeagent-deployer 镜像")
}

func TestEnsureAgentUpgrade_isPatchVersion(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	tests := []struct {
		version string
		want    bool
	}{
		// 有效的 patch 版本（Patch > 0 且无预发布）
		{"v1.2.3", true},
		{"1.2.3", true},
		{"v2.10.5", true},

		// 非 patch 版本（Patch == 0）
		{"v1.2.0", false},
		{"1.2.0", false},

		// 预发布版本（即使 Patch > 0）
		{"v1.2.3-rc1", false},
		{"1.2.3-beta", false},

		// 无效版本格式
		{"invalid", false},
		{"v1.2", false},
		{"", false},
		{"v1", false},
	}

	for _, tt := range tests {
		t.Run(tt.version, func(t *testing.T) {
			assert.Equal(t, tt.want, e.isPatchVersion(tt.version))
		})
	}
}

func TestEnsureAgentUpgrade_extractVersionFromImage(t *testing.T) {
	e := &EnsureAgentUpgrade{}

	tests := []struct {
		image    string
		expected string
	}{
		{"nginx:latest", "latest"},
		{"registry.example.com/bkeagent-deployer:v2.1.0", "v2.1.0"},
		{"alpine", ""},          // 无 tag
		{"redis:6.2:extra", ""}, // 多个冒号
		{"", ""},                // 空字符串
		{":invalid", "invalid"}, // 只有 tag（边界情况）
	}

	for _, tt := range tests {
		t.Run(tt.image, func(t *testing.T) {
			got := e.extractVersionFromImage(tt.image)
			assert.Equal(t, tt.expected, got)
		})
	}
}

func Test_podHasImage(t *testing.T) {
	tests := []struct {
		name        string
		pod         corev1.Pod
		targetImage string
		expected    bool
	}{
		{
			name: "image found",
			pod: corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{Name: "nginx", Image: "nginx:1.21"},
						{Name: "deployer", Image: "bkeagent-deployer:v2.1.0"},
					},
				},
			},
			targetImage: "bkeagent-deployer:v2.1.0",
			expected:    true,
		},
		{
			name: "image not found",
			pod: corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{Name: "nginx", Image: "nginx:1.21"},
					},
				},
			},
			targetImage: "redis:6.2",
			expected:    false,
		},
		{
			name:        "empty pod",
			pod:         corev1.Pod{},
			targetImage: "any",
			expected:    false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := podHasImage(tt.pod, tt.targetImage)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func Test_podIsReady(t *testing.T) {
	tests := []struct {
		name     string
		pod      corev1.Pod
		expected bool
	}{
		{
			name: "ready condition true",
			pod: corev1.Pod{
				Status: corev1.PodStatus{
					Conditions: []corev1.PodCondition{
						{Type: corev1.PodReady, Status: corev1.ConditionTrue},
					},
				},
			},
			expected: true,
		},
		{
			name: "ready condition false",
			pod: corev1.Pod{
				Status: corev1.PodStatus{
					Conditions: []corev1.PodCondition{
						{Type: corev1.PodReady, Status: corev1.ConditionFalse},
					},
				},
			},
			expected: false,
		},
		{
			name: "no ready condition",
			pod: corev1.Pod{
				Status: corev1.PodStatus{
					Conditions: []corev1.PodCondition{
						{Type: corev1.PodScheduled, Status: corev1.ConditionTrue},
					},
				},
			},
			expected: false,
		},
		{
			name:     "empty pod",
			pod:      corev1.Pod{},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := podIsReady(tt.pod)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestEnsureAgentUpgrade_updateBKEAgentDeployerAddonStatus_AddNew(t *testing.T) {
	// 1. 创建 fake client
	fakeClient := fake.NewClientBuilder().Build()

	// 2. 创建非 nil 的 logger（关键！）
	logger := &bkev1beta1.BKELogger{}

	// 3. 完整初始化 PhaseContext
	phaseCtx := &phaseframe.PhaseContext{
		Client: fakeClient,
		Log:    logger, // ← 必须非 nil
		// Ctx 和 Scheme 可选，但建议设置
		Scheme: runtime.NewScheme(),
	}

	// 4. 创建 EnsureAgentUpgrade
	e := &EnsureAgentUpgrade{
		BasePhase: phaseframe.NewBasePhase(phaseCtx, "test-phase"),
	}

	// 5. 构造 BKECluster
	bkeCluster := &bkev1beta1.BKECluster{
		Status: confv1beta1.BKEClusterStatus{
			AddonStatus: []confv1beta1.Product{},
		},
	}

	// 6. mock SyncStatusUntilComplete
	patch := gomonkey.ApplyFunc(
		mergecluster.SyncStatusUntilComplete,
		func(_ client.Client, _ *bkev1beta1.BKECluster, _ ...mergecluster.PatchFunc) error {
			return nil
		},
	)
	defer patch.Reset()

	// 7. 执行测试
	err := e.updateBKEAgentDeployerAddonStatus(bkeCluster, "v3.0.1")
	assert.NoError(t, err)
	assert.Len(t, bkeCluster.Status.AddonStatus, 1)
	assert.Equal(t, "bkeagent-deployer", bkeCluster.Status.AddonStatus[0].Name)
	assert.Equal(t, "v3.0.1", bkeCluster.Status.AddonStatus[0].Version)
}
